// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <cstdint>
#include <iosfwd>
#include <limits>
#include <string>
#include <string_view>
#include <utility>


#include <turbo/utility/status.h>
#include <nebula/types/type_fwd.h>
#include <nebula/numeric/basic_decimal.h>

namespace nebula {

    /// Represents a signed 128-bit integer in two's complement.
    /// Calculations wrap around and overflow is ignored.
    /// The max decimal precision that can be safely represented is
    /// 38 significant digits.
    ///
    /// For a discussion of the algorithms, look at Knuth's volume 2,
    /// Semi-numerical Algorithms section 4.3.1.
    ///
    /// Adapted from the Apache ORC C++ implementation
    ///
    /// The implementation is split into two parts :
    ///
    /// 1. BasicDecimal128
    ///    - can be safely compiled to IR without references to libstdc++.
    /// 2. Decimal128
    ///    - has additional functionality on top of BasicDecimal128 to deal with
    ///      strings and streams.
    class TURBO_EXPORT Decimal128 : public BasicDecimal128 {
    public:
        /// \cond FALSE
        // (need to avoid a duplicate definition in Sphinx)
        using BasicDecimal128::BasicDecimal128;
        /// \endcond

        /// \brief constructor creates a Decimal128 from a BasicDecimal128.
        constexpr Decimal128(const BasicDecimal128 &value) noexcept  // NOLINT runtime/explicit
                : BasicDecimal128(value) {}

        /// \brief Parse the number from a base 10 string representation.
        explicit Decimal128(const std::string &value);

        /// \brief Empty constructor creates a Decimal128 with a value of 0.
        // This is required on some older compilers.
        constexpr Decimal128() noexcept: BasicDecimal128() {}

        /// Divide this number by right and return the result.
        ///
        /// This operation is not destructive.
        /// The answer rounds to zero. Signs work like:
        ///   21 /  5 ->  4,  1
        ///  -21 /  5 -> -4, -1
        ///   21 / -5 -> -4,  1
        ///  -21 / -5 ->  4, -1
        /// \param[in] divisor the number to divide by
        /// \return the pair of the quotient and the remainder
        turbo::Result<std::pair<Decimal128, Decimal128>> Divide(const Decimal128 &divisor) const {
            std::pair<Decimal128, Decimal128> result;
            auto dstatus = BasicDecimal128::Divide(divisor, &result.first, &result.second);
            TURBO_RETURN_NOT_OK(ToArrowStatus(dstatus));
            return result;
        }

        /// \brief Convert the Decimal128 value to a base 10 decimal string with the given
        /// scale.
        std::string to_string(int32_t scale) const;

        /// \brief Convert the value to an integer string
        std::string ToIntegerString() const;

        /// \brief Cast this value to an int64_t.
        explicit operator int64_t() const;

        /// \brief Convert a decimal string to a Decimal128 value, optionally including
        /// precision and scale if they're passed in and not null.
        static turbo::Status from_string(std::string_view s, Decimal128 *out, int32_t *precision,
                                        int32_t *scale = nullptr);

        static turbo::Status from_string(const std::string &s, Decimal128 *out, int32_t *precision,
                                        int32_t *scale = nullptr);

        static turbo::Status from_string(const char *s, Decimal128 *out, int32_t *precision,
                                        int32_t *scale = nullptr);

        static turbo::Result<Decimal128> from_string(std::string_view s);

        static turbo::Result<Decimal128> from_string(const std::string &s);

        static turbo::Result<Decimal128> from_string(const char *s);

        static turbo::Result<Decimal128> FromReal(double real, int32_t precision, int32_t scale);

        static turbo::Result<Decimal128> FromReal(float real, int32_t precision, int32_t scale);

        /// \brief Convert from a big-endian byte representation. The length must be
        ///        between 1 and 16.
        /// \return error status if the length is an invalid value
        static turbo::Result<Decimal128> FromBigEndian(const uint8_t *data, int32_t length);

        /// \brief Convert Decimal128 from one scale to another
        turbo::Result<Decimal128> Rescale(int32_t original_scale, int32_t new_scale) const {
            Decimal128 out;
            auto dstatus = BasicDecimal128::Rescale(original_scale, new_scale, &out);
            TURBO_RETURN_NOT_OK(ToArrowStatus(dstatus));
            return out;
        }

        /// \brief Convert to a signed integer
        template<typename T, typename = internal::EnableIfIsOneOf<T, int32_t, int64_t>>
        turbo::Result<T> ToInteger() const {
            constexpr auto min_value = std::numeric_limits<T>::min();
            constexpr auto max_value = std::numeric_limits<T>::max();
            const auto &self = *this;
            if (self < min_value || self > max_value) {
                return turbo::invalid_argument_error("Invalid cast from Decimal128 to ", sizeof(T),
                                              " byte integer");
            }
            return static_cast<T>(low_bits());
        }

        /// \brief Convert to a signed integer
        template<typename T, typename = internal::EnableIfIsOneOf<T, int32_t, int64_t>>
        turbo::Status ToInteger(T *out) const {
            return ToInteger<T>().try_value(out);
        }

        /// \brief Convert to a floating-point number (scaled)
        float ToFloat(int32_t scale) const;

        /// \brief Convert to a floating-point number (scaled)
        double ToDouble(int32_t scale) const;

        /// \brief Convert to a floating-point number (scaled)
        template<typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
        T ToReal(int32_t scale) const {
            static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>,
                          "Unexpected floating-point type");
            if constexpr (std::is_same_v<T, float>) {
                return ToFloat(scale);
            } else {
                return ToDouble(scale);
            }
        }

        friend std::ostream &operator<<(std::ostream &os,
                                        const Decimal128 &decimal);

    private:
        /// Converts internal error code to turbo::Status
        turbo::Status ToArrowStatus(DecimalStatus dstatus) const;
    };

    /// Represents a signed 256-bit integer in two's complement.
    /// The max decimal precision that can be safely represented is
    /// 76 significant digits.
    ///
    /// The implementation is split into two parts :
    ///
    /// 1. BasicDecimal256
    ///    - can be safely compiled to IR without references to libstdc++.
    /// 2. Decimal256
    ///    - (TODO) has additional functionality on top of BasicDecimal256 to deal with
    ///      strings and streams.
    class TURBO_EXPORT Decimal256 : public BasicDecimal256 {
    public:
        /// \cond FALSE
        // (need to avoid a duplicate definition in Sphinx)
        using BasicDecimal256::BasicDecimal256;
        /// \endcond

        /// \brief constructor creates a Decimal256 from a BasicDecimal256.
        constexpr Decimal256(const BasicDecimal256 &value) noexcept  // NOLINT(runtime/explicit)
                : BasicDecimal256(value) {}

        /// \brief Parse the number from a base 10 string representation.
        explicit Decimal256(const std::string &value);

        /// \brief Empty constructor creates a Decimal256 with a value of 0.
        // This is required on some older compilers.
        constexpr Decimal256() noexcept: BasicDecimal256() {}

        /// \brief Convert the Decimal256 value to a base 10 decimal string with the given
        /// scale.
        std::string to_string(int32_t scale) const;

        /// \brief Convert the value to an integer string
        std::string ToIntegerString() const;

        /// \brief Convert a decimal string to a Decimal256 value, optionally including
        /// precision and scale if they're passed in and not null.
        static turbo::Status from_string(std::string_view s, Decimal256 *out, int32_t *precision,
                                        int32_t *scale = nullptr);

        static turbo::Status from_string(const std::string &s, Decimal256 *out, int32_t *precision,
                                        int32_t *scale = nullptr);

        static turbo::Status from_string(const char *s, Decimal256 *out, int32_t *precision,
                                        int32_t *scale = nullptr);

        static turbo::Result<Decimal256> from_string(std::string_view s);

        static turbo::Result<Decimal256> from_string(const std::string &s);

        static turbo::Result<Decimal256> from_string(const char *s);

        /// \brief Convert Decimal256 from one scale to another
        turbo::Result<Decimal256> Rescale(int32_t original_scale, int32_t new_scale) const {
            Decimal256 out;
            auto dstatus = BasicDecimal256::Rescale(original_scale, new_scale, &out);
            TURBO_RETURN_NOT_OK(ToArrowStatus(dstatus));
            return out;
        }

        /// Divide this number by right and return the result.
        ///
        /// This operation is not destructive.
        /// The answer rounds to zero. Signs work like:
        ///   21 /  5 ->  4,  1
        ///  -21 /  5 -> -4, -1
        ///   21 / -5 -> -4,  1
        ///  -21 / -5 ->  4, -1
        /// \param[in] divisor the number to divide by
        /// \return the pair of the quotient and the remainder
        turbo::Result<std::pair<Decimal256, Decimal256>> Divide(const Decimal256 &divisor) const {
            std::pair<Decimal256, Decimal256> result;
            auto dstatus = BasicDecimal256::Divide(divisor, &result.first, &result.second);
            TURBO_RETURN_NOT_OK(ToArrowStatus(dstatus));
            return result;
        }

        /// \brief Convert from a big-endian byte representation. The length must be
        ///        between 1 and 32.
        /// \return error status if the length is an invalid value
        static turbo::Result<Decimal256> FromBigEndian(const uint8_t *data, int32_t length);

        static turbo::Result<Decimal256> FromReal(double real, int32_t precision, int32_t scale);

        static turbo::Result<Decimal256> FromReal(float real, int32_t precision, int32_t scale);

        /// \brief Convert to a floating-point number (scaled).
        /// May return infinity in case of overflow.
        float ToFloat(int32_t scale) const;

        /// \brief Convert to a floating-point number (scaled)
        double ToDouble(int32_t scale) const;

        /// \brief Convert to a floating-point number (scaled)
        template<typename T, typename = std::enable_if_t<std::is_floating_point_v<T>>>
        T ToReal(int32_t scale) const {
            static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>,
                          "Unexpected floating-point type");
            if constexpr (std::is_same_v<T, float>) {
                return ToFloat(scale);
            } else {
                return ToDouble(scale);
            }
        }

        friend std::ostream &operator<<(std::ostream &os,
                                        const Decimal256 &decimal);

    private:
        /// Converts internal error code to turbo::Status
        turbo::Status ToArrowStatus(DecimalStatus dstatus) const;
    };

    /// For an integer type, return the max number of decimal digits
    /// (=minimal decimal precision) it can represent.
    inline turbo::Result<int32_t> MaxDecimalDigitsForInteger(Type::type type_id) {
        switch (type_id) {
            case Type::INT8:
            case Type::UINT8:
                return 3;
            case Type::INT16:
            case Type::UINT16:
                return 5;
            case Type::INT32:
            case Type::UINT32:
                return 10;
            case Type::INT64:
                return 19;
            case Type::UINT64:
                return 20;
            default:
                break;
        }
        return turbo::invalid_argument_error("Not an integer type: ", type_id);
    }

}  // namespace nebula
